feat(transport): add Streamable HTTP /mcp endpoint (FT-1935)#21
feat(transport): add Streamable HTTP /mcp endpoint (FT-1935)#21joalves wants to merge 8 commits into
Conversation
WalkthroughThis pull request extends the MCP server with HTTP/Streamable transport support via a Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Warning There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure. 🔧 ESLint
ESLint skipped: no ESLint configuration detected in root package.json. To enable, add Comment |
There was a problem hiding this comment.
Actionable comments posted: 3
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/index.ts (1)
11-11:⚠️ Potential issue | 🔴 Critical | ⚡ Quick winFix the missing module import before merge (Line 11).
CI is currently blocked by
TS2307: Cannot find module './dxt-bundle'. Please add/restore the module (or correct the import path) so typecheck passes.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/index.ts` at line 11, The import for DXT_BUNDLE_BASE64 and DXT_BUNDLE_SHA in src/index.ts cannot be resolved; restore or correct the module so TypeScript can find it by either adding the missing module file that exports DXT_BUNDLE_BASE64 and DXT_BUNDLE_SHA or updating the import path to the correct location where those named exports are defined (and ensure the module actually exports those symbols).
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@README.md`:
- Around line 446-448: The blockquote contains an undesired blank line causing
markdownlint MD028; remove the empty line between the two quoted lines so the
two `>` lines are contiguous (or convert the second quoted line into a normal
paragraph). Edit the block that currently has the two separate `>` lines (the
SSE transport note and the Reload the IDE window note) to be adjacent `>` lines
with no blank line between them so the blockquote is continuous.
In `@src/index.ts`:
- Around line 778-788: The code uses hardcoded path literals "/sse" and "/mcp"
when registering handlers and dispatching (see pathPrefix properties passed to
handleMcpTransportRequest and the url.pathname.startsWith checks); define
top-level ALL_CAPS constants (e.g., const SSE_PATH = "/sse"; const MCP_PATH =
"/mcp";) near other defaults and replace all inline occurrences with those
constants so sseMcpHandler, streamableMcpHandler, handleMcpTransportRequest and
any url.pathname.startsWith checks reference SSE_PATH and MCP_PATH instead of
string literals to prevent drift.
In `@src/shared.ts`:
- Line 106: Promote the inline array ['/sse', '/mcp'] into a top-level ALL_CAPS
constant (e.g., TRANSPORT_PREFIXES) declared with the other shared constants,
and replace the inline usage in the extractEndpointFromPath call so that
endpointFromPath is produced by extractEndpointFromPath(url.pathname,
TRANSPORT_PREFIXES); update any imports/exports if needed to keep naming
consistent.
---
Outside diff comments:
In `@src/index.ts`:
- Line 11: The import for DXT_BUNDLE_BASE64 and DXT_BUNDLE_SHA in src/index.ts
cannot be resolved; restore or correct the module so TypeScript can find it by
either adding the missing module file that exports DXT_BUNDLE_BASE64 and
DXT_BUNDLE_SHA or updating the import path to the correct location where those
named exports are defined (and ensure the module actually exports those
symbols).
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 6f99c141-1621-43af-bfd9-fbd36f5cf9a1
📒 Files selected for processing (5)
README.mdsrc/index.tssrc/shared.tstests/unit/shared.test.tstests/unit/streamable-routing.test.ts
| > SSE transport (`/sse`) is **not** supported by this connector. The legacy Gemini CLI / Code Assist sections above continue to use `/sse` until those clients add Streamable HTTP support. | ||
|
|
||
| > Reload the IDE window after editing settings (VS Code: Command Palette → **Developer: Reload Window**). MCP support in Code Assist requires **agent preview mode** — set `"geminicodeassist.updateChannel": "Insiders"` in VS Code settings if not already enabled. |
There was a problem hiding this comment.
Fix markdownlint MD028: remove the blank line inside the blockquote.
There is an empty line between two quoted lines, which triggers no-blanks-blockquote. Keep both quoted lines contiguous (or make the second one a normal paragraph).
Suggested fix
> SSE transport (`/sse`) is **not** supported by this connector. The legacy Gemini CLI / Code Assist sections above continue to use `/sse` until those clients add Streamable HTTP support.
-
> Reload the IDE window after editing settings (VS Code: Command Palette → **Developer: Reload Window**). MCP support in Code Assist requires **agent preview mode** — set `"geminicodeassist.updateChannel": "Insiders"` in VS Code settings if not already enabled.📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| > SSE transport (`/sse`) is **not** supported by this connector. The legacy Gemini CLI / Code Assist sections above continue to use `/sse` until those clients add Streamable HTTP support. | |
| > Reload the IDE window after editing settings (VS Code: Command Palette → **Developer: Reload Window**). MCP support in Code Assist requires **agent preview mode** — set `"geminicodeassist.updateChannel": "Insiders"` in VS Code settings if not already enabled. | |
| > SSE transport (`/sse`) is **not** supported by this connector. The legacy Gemini CLI / Code Assist sections above continue to use `/sse` until those clients add Streamable HTTP support. | |
| > Reload the IDE window after editing settings (VS Code: Command Palette → **Developer: Reload Window**). MCP support in Code Assist requires **agent preview mode** — set `"geminicodeassist.updateChannel": "Insiders"` in VS Code settings if not already enabled. |
🧰 Tools
🪛 markdownlint-cli2 (0.22.1)
[warning] 447-447: Blank line inside blockquote
(MD028, no-blanks-blockquote)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@README.md` around lines 446 - 448, The blockquote contains an undesired blank
line causing markdownlint MD028; remove the empty line between the two quoted
lines so the two `>` lines are contiguous (or convert the second quoted line
into a normal paragraph). Edit the block that currently has the two separate `>`
lines (the SSE transport note and the Reload the IDE window note) to be adjacent
`>` lines with no blank line between them so the blockquote is continuous.
| { pathPrefix: "/sse", handler: sseMcpHandler }, | ||
| { apiKey, endpoint }, | ||
| clientFingerprint | ||
| ); | ||
| } | ||
|
|
||
| return await oauthProvider.fetch(request, env, ctx); | ||
| if (url.pathname.startsWith("/mcp")) { | ||
| return await handleMcpTransportRequest( | ||
| request, env, ctx, | ||
| { pathPrefix: "/mcp", handler: streamableMcpHandler }, | ||
| { apiKey, endpoint }, |
There was a problem hiding this comment.
🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win
Replace inline transport path literals with top-level constants (Line 778 and Line 787).
"/sse" and "/mcp" are hardcoded in the route objects. Move these to grouped ALL_CAPS constants and reuse them across handler registration and dispatch to avoid drift.
As per coding guidelines, "Never use magic strings or hardcoded values inline in code - all default values must be declared as constants at the top of the file".
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/index.ts` around lines 778 - 788, The code uses hardcoded path literals
"/sse" and "/mcp" when registering handlers and dispatching (see pathPrefix
properties passed to handleMcpTransportRequest and the url.pathname.startsWith
checks); define top-level ALL_CAPS constants (e.g., const SSE_PATH = "/sse";
const MCP_PATH = "/mcp";) near other defaults and replace all inline occurrences
with those constants so sseMcpHandler, streamableMcpHandler,
handleMcpTransportRequest and any url.pathname.startsWith checks reference
SSE_PATH and MCP_PATH instead of string literals to prevent drift.
| const authHeader = request.headers.get("Authorization"); | ||
|
|
||
| const endpointFromPath = extractEndpointFromPath(url.pathname, '/sse'); | ||
| const endpointFromPath = extractEndpointFromPath(url.pathname, ['/sse', '/mcp']); |
There was a problem hiding this comment.
🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win
Extract transport prefixes into a top-level constant (Line 106).
Using ['/sse', '/mcp'] inline introduces a magic value in a changed path. Please promote this into an ALL_CAPS constant near the other shared constants and reuse it here.
As per coding guidelines, "Never use magic strings or hardcoded values inline in code - all default values must be declared as constants at the top of the file".
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/shared.ts` at line 106, Promote the inline array ['/sse', '/mcp'] into a
top-level ALL_CAPS constant (e.g., TRANSPORT_PREFIXES) declared with the other
shared constants, and replace the inline usage in the extractEndpointFromPath
call so that endpointFromPath is produced by
extractEndpointFromPath(url.pathname, TRANSPORT_PREFIXES); update any
imports/exports if needed to keep naming consistent.
Summary
https://mcp.absmartly.com/mcpalongside the existing SSE transport at/sse. Both wrap the sameMCP_OBJECTDurable Object, so every tool, resource, and prompt is exposed identically on both transports./sseauth/dispatch block insrc/index.tsinto a reusablehandleMcpTransportRequesthelper so both transports share the API-key bypass + OAuth 401 flow. Pure behavior preservation for/sseclients.extractEndpointFromPathto acceptstring | readonly string[]and teachesdetectApiKeyto recognise endpoint segments under/mcp/<subdomain>just like/sse/<subdomain>./mcpinstall for Claude Code, Cursor, Windsurf, and VS Code, leaving the SSE one-click install badges untouched for now.Why now?
The MCP spec deprecated SSE in March 2025 in favour of Streamable HTTP. Adding
/mcpis additive — no migration cost for existing/sseclients (mcp-remote, the DXT extension, Claude Desktop's connector UI).JIRA
FT-1935
Test Plan
npm run test:unit)npm run typecheck)c0edfd7d-8cf5-49e6-ae85-7fef5af5e711); DXT SHA matches local buildcurl -sI https://mcp.absmartly.com/sse→HTTP/2 401withwww-authenticate: Bearer realm="OAuth"(no regression)curl -sI https://mcp.absmartly.com/mcp→HTTP/2 401withwww-authenticate: Bearer realm="OAuth"(new)wrangler devsmoke test: both/sseand/mcp401 as expectedmcp-remote --transport http https://mcp.absmartly.com/mcp ..., Cursor with"type":"http", and Gemini Enterprise's Custom MCP connectorPlan
The full implementation plan is in
docs/superpowers/plans/2026-05-20-streamable-http-transport.md(local-only, gitignored). It was executed in 8 TDD-style commits via subagent-driven development.Follow-ups (not in this PR)
From the final code review pass, captured for separate work:
scripts/deploy-with-verify.shonly verifies DXT asset SHA, not worker route behaviour. Task 8 caught/mcpdrifting to 404 between Task 6's deploy and Task 8's check — a redeploy fixed it. Adding a 401 +WWW-Authenticatecurl assertion to the verify script (~5 lines of bash) would catch silent worker rollbacks.url.pathname.startsWith("/mcp")over-matches (e.g. would catch/mcp.json). Same issue exists for/sse. Tighten tourl.pathname === "/mcp" || url.pathname.startsWith("/mcp/")for both routes./mcp/<subdomain>path-style URLs would 405 at the handler.McpAgent.serve("/mcp", ...)uses an exactURLPattern({ pathname: "/mcp" }).detectApiKeyextracts the endpoint correctly, but the transport handler itself doesn't accept the path suffix. Document this as a known limitation (clients must use/mcpwithx-absmartly-endpointheader), or mountserve("/mcp/*")if path-style is needed.fetchhandler actually dispatches/mcptostreamableMcpHandler. The 3 new unit tests cover helper inputs; the dispatch wiring is currently verified only by production smoke tests. ~15-line test addition.apiHandlerscovering/.well-known/oauth-protected-resourceis inaccurate.@cloudflare/workers-oauth-providerdoesn't actually serve that endpoint — only/.well-known/oauth-authorization-server. Pre-existing; not made worse by this PR. If a Streamable HTTP client (Gemini Enterprise?) reports a resource-indicator mismatch, that's the root cause./sse→/mcpwith"type":"http"once the new endpoint has been stable in production for ~1 week.Summary by CodeRabbit
Release Notes
New Features
Documentation
Tests